import com.google.gson.*;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 忽略顺序比较两个JSON字符串是否相等
 * <p>
 * 当比较两个对象数组时需要注意，如果理应认为相等的两个对象属性的顺序不一致的话，那么解析后的数组顺序也不一致，这将导致两数组不相同
 * 例如：
 * {name:admin, password:123456} 和 {password:123456, name:admin} 单独比较是相等的，但如果作为数组的元素比较，所在索引值很可能不一样
 */
public class JsonSameUtil {
	private final Gson gson = new Gson();
	private final JsonParser parser = new JsonParser();
	// a是否包含b，如果是比较时只用满足b的数据a都有且相等即可(只限于对象的属性)
	private boolean aContainB;
	private boolean isIgnoreStrSpace; //字符串比较不考虑空格
	private boolean isArrayIgnoreOrder; //数组不考虑顺序

	public static void main(String[] args) {
		String s1 = "";
		String s2 = "";//另一个JSON字符串
		String same = new JsonSameUtil().same(s1, s2);
		System.out.println(same == null ? true : same);
	}

	/**
	 * 比较两个JSON字符串是否相等，相等时返回null
	 *
	 * @param a JSON字符串a
	 * @param b JSON字符串b
	 * @return 相等就返回null，否则返回不相等原因
	 */
	public String same(String a, String b) {
		if (a == null) {
			return b == null ? null : "其中一个对象为null，另一个对象不为null";
		}
		if (a.equals(b)) {
			return null;
		}
		JsonElement aElement = null;
		JsonElement bElement = null;
		try {
			aElement = parser.parse(a);
			bElement = parser.parse(b);
		} catch (JsonSyntaxException e) {
			return "解析失败，请确认字符串格式是否正确";
		}
		if (gson.toJson(aElement).equals(gson.toJson(bElement))) {
			return null;
		}
		return same(aElement, bElement);
	}

	private String same(JsonElement a, JsonElement b) {
		if (a.isJsonObject() && b.isJsonObject()) {
			return same((JsonObject) a, (JsonObject) b);
		} else if (a.isJsonArray() && b.isJsonArray()) {
			return same((JsonArray) a, (JsonArray) b);
		} else if (a.isJsonPrimitive() && b.isJsonPrimitive()) {
			return same((JsonPrimitive) a, (JsonPrimitive) b);
		} else if (a.isJsonNull() && b.isJsonNull()) {
			return same((JsonNull) a, (JsonNull) b);
		} else {
			return "未知类型，判定为不相等:\na: " + a + "\nb: " + b;
		}
	}

	private String same(JsonObject a, JsonObject b) {
		// 获取对象的key集合
		Set<String> aSet = a.entrySet().stream().map(Map.Entry::getKey).collect(Collectors.toSet());
		Set<String> bSet = b.entrySet().stream().map(Map.Entry::getKey).collect(Collectors.toSet());

		// 如果key不相等，或者a不包含b，就说明不一样
		if (!aSet.equals(bSet) && !(aContainB && aSet.containsAll(bSet))) {
			String res = "对象key不相等: ";
			if (aSet.size() != bSet.size()) { //个数不相等
				res += (aSet.size() > bSet.size() ? "a" : "b") + " 对象多了键: \n";
				if (aSet.size() > bSet.size()) {
					aSet.removeAll(bSet);
					res += aSet;
				} else {
					bSet.removeAll(aSet);
					res += bSet;
				}
			} else { //值不相等
				Set<String> set = new HashSet<>(aSet);
				aSet.removeAll(bSet);
				bSet.removeAll(set);
				res += "键不相同: \na: " + aSet + "\nb: " + bSet;
			}
			return res;
		}
		// 进一步比较值，遍历b集合
		for (String bKey : bSet) {
			JsonElement aKey = a.get(bKey);
			if (aKey == null) continue;
			String res = same(aKey, b.get(bKey));
			if (res != null)
				return bKey + ": " + res;
		}
		return null;
	}

	private String same(JsonArray a, JsonArray b) {
		List<JsonElement> aList = toSortedList(a);
		List<JsonElement> bList = toSortedList(b);

		if (a.size() != b.size()) {
			return "数组元素个数不相等: \na(" + aList.size() + "): " + aList + "\nb(" + bList.size() + "): " + bList;
		}

		if (isArrayIgnoreOrder) {
			boolean[] vis = new boolean[bList.size()];
			Arrays.fill(vis, false);
			for (int i = 0; i < bList.size(); i++) {
				String res = "";
				for (int j = 0; j < aList.size(); j++) {
					if (vis[j]) continue;
					res = same(bList.get(i), aList.get(j));
					if (res == null) {
						vis[j] = true;
						break;
					}
					if (j == aList.size() - 1)
						return "[" + i + "]: " + res;
				}
			}

		} else {
			for (int i = 0; i < aList.size(); i++) {
				String res = same(aList.get(i), bList.get(i));
				if (res != null) {
					return "[" + i + "]: " + res;
				}
			}
		}

		return null;
	}

	private String same(JsonPrimitive a, JsonPrimitive b) {
		if (a.equals(b)) return null;
		String sa = a.getAsString();
		String sb = b.getAsString();
		if (a.isString() && isIgnoreStrSpace &&
				sa.replace(" ", "").equals(sb.replace(" ", "")))
			return null;
		return sa + " != " + sb;
	}

	private String same(JsonNull a, JsonNull b) {
		return null;
	}

	private List<JsonElement> toSortedList(JsonArray a) {
		List<JsonElement> aList = new ArrayList<>();
		a.forEach(aList::add);
		// aList.sort(Comparator.comparing(gson::toJson)); //不要排序
		return aList;
	}

	public boolean isaContainB() {
		return aContainB;
	}

	public JsonSameUtil setaContainB(boolean aContainB) {
		this.aContainB = aContainB;
		return this;
	}

	public boolean isIgnoreStrSpace() {
		return isIgnoreStrSpace;
	}

	public JsonSameUtil setIgnoreStrSpace(boolean ignoreStrSpace) {
		isIgnoreStrSpace = ignoreStrSpace;
		return this;
	}

	public boolean isArrayIgnoreOrder() {
		return isArrayIgnoreOrder;
	}

	public JsonSameUtil setArrayIgnoreOrder(boolean arrayIgnoreOrder) {
		isArrayIgnoreOrder = arrayIgnoreOrder;
		return this;
	}
}
 